package jpave.query_interface;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.net.InetAddress;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Collections;
import java.util.UUID;
import java.util.Vector;
import java.util.prefs.AbstractPreferences;
import java.util.prefs.Preferences;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import assem.DBConn;
import jpave.database.JPaveDBWrapper;
import jpave.dataholders.AssemblyData;
import jpave.dataholders.BasicQueryData;
import jpave.dataholders.MainData;
import jpave.dataholders.ContigData;
import jpave.dataholders.DatabaseData;
import jpave.dataholders.FilterContigData;
import jpave.dataholders.GlobalSettings;
import jpave.dataholders.QueryData;
import jpave.dataholders.SessionData;
import jpave.manager.ManagerFrame;
//import util.internet.CookiePreferences;
//import util.internet.CookieUtil;
import util.Debug.ErrorReport;
import util.methods.ArrayHelpers;
import util.methods.Converters;
import util.methods.HostsCfg;
import util.methods.TimeHelpers;
import util.methods.UIHelpers;
import util.methods.Version;
import util.ui.DisplayFloat;
import util.ui.HiddenTabbedPane;
import util.ui.MenuTree;
import util.ui.MenuTreeNode;
import util.ui.UserPrompt;
import util.ui.MenuTreeNode.MenuTreeNodeEvent;
import util.ui.MenuTreeNode.MenuTreeNodeListener;

import java.lang.OutOfMemoryError;

/**
 *  XXX The Display All Assemblies window methods
 *   XXX  Single assembly window
 *   		// XXX Execute the query...
 * 
 */
public class JPaveFrame extends JFrame {
	private static final boolean debug = false;
	private static final Color bgColor = Color.white; 
	// if change here, also change in util.ui.MenuTreeNode
	private static final Color bgColorLeft = Color.white; 
	private static final String title = "viewSingleTCW";
	
	private static Vector<JPaveFrame> openFrames = null;
	// Always uses the same preference file. 
	//     So if in conflict with earlier versions, change name.
	String prefRootName = "viewSingleTCW"; 
	
	/**
	 *  Open the Assembly List Window
	 */
	public JPaveFrame(GlobalSettings inSettings) {
		long time = System.currentTimeMillis();
		if(openFrames == null) {
			openFrames = new Vector<JPaveFrame> ();
		}
		openFrames.add(this);
		theSettings = inSettings;
		initialize();

		if (theSettings.isApplet()) {
			UIHelpers.setApplet(theSettings.getApplet());
			openAssembly(null, theSettings.getSelectedSession());
			setVisible(true);
			System.err.println("Startup time " + TimeHelpers.getElapsedTimeStr(time));
			System.err.println("Viewing  " + selectedSession.getViewDescription());
		} else
			displayAssemblies(); // Setup for the window that shows all assemblies
		
	}

	/**
	 * Open Single Assembly: started with an assembly name as argument. 
	 */
	public JPaveFrame(GlobalSettings inSettings, String assemblyPath) {
		long time = System.currentTimeMillis();
		if(openFrames == null) {
			openFrames = new Vector<JPaveFrame> ();
		}
		openFrames.add(this);
		theSettings = inSettings;
		initialize();

		// Parse assemblyID path: may have just the assembly ID or
		// something like host:database:assemblyID.
		String[] tokens = assemblyPath.split(":");

		String hostName = null;
		String dbName = null;
		String assemblyID = null;

		// hostName needs to be the full path to work. Should add checks.
		if (tokens.length > 0) {
			if (tokens.length > 2)
				hostName = tokens[tokens.length - 3];
			if (tokens.length > 1)
				dbName = tokens[tokens.length - 2];
			if (tokens.length > 0)
				assemblyID = tokens[tokens.length - 1];

		}
		if (hostName != null && hostName.trim().equals(""))
		{
			hostName = null;
		}
		// give assembly name and uses local machine
		if (hostName == null) {
			HostsCfg hosts = new HostsCfg();
			hostName = hosts.host;
		}

		// Find session with specified assembly
		selectedSession = null;
		Vector<SessionData> sessions = SessionData
				.getAssemblySessions(theSettings.getAllSessions());
		for (SessionData session : sessions) {
			AssemblyData assembly = session.getAssembly(0);
			if (assembly == null)break;
			
			DatabaseData db = assembly.getDBData();
			if (db == null) break;

			if ((dbName == null || db.getDBName().equals(dbName))
				&& (assembly.getAssemblyID().equals(assemblyID))) {
				    selectedSession = session;
				    dbName = assembly.getDBData().getDBName();
				    if (!goodVersion(session)) System.exit(-1);
				    break;
			}
		}

		if (selectedSession != null) {
			openAssembly(null, selectedSession);
		}
		else {
			ErrorReport.die("Error: Could not find database with ID=" + assemblyPath + 
					" (The database name and ID may be different)");
		}
		System.err.println("Startup time " + TimeHelpers.getElapsedTimeStr(time));
		System.err.println("Viewing  " + selectedSession.getViewDescription());
	}
	
	/**************************************************
	 * called when Jpave is started, either for Assemblies window or single assembly
	 * i.e. not called from Assemblies window for subsequence assembly windows
	 */
	private void initialize() {
		System.err.println("Initialize...");
		if(theFramesList == null)
			theFramesList = new JPaveFrameManager();
		
		nFrameID = theFramesList.registerFrame(this);
		
		lastSaveFilePath = System.getProperty("user.dir"); // CAS 12/22/14
		
		// Make sure temp directory exists
		File testFile = new File(GlobalSettings.getJPaveTempPath());

		if (!testFile.exists()) {
			if (!testFile.mkdir()) {
				System.err.println("Failed to create temp directory '"
						+ testFile.getAbsolutePath() + "'.");
				return;
			}
		}
		
		// Add shutdown handler to remove cache files
		MyShutdown sh = new MyShutdown();
		Runtime.getRuntime().addShutdownHook(sh);

		strUniqueID = String.valueOf(UUID.randomUUID());
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

		// Load window dimensions from preferences
		Preferences userPrefs = getPreferencesRoot();
		int nX = userPrefs.getInt("frame_win_x", Integer.MAX_VALUE);
		int nY = userPrefs.getInt("frame_win_y", Integer.MAX_VALUE);
		int nWidth = userPrefs.getInt("frame_win_width", Integer.MAX_VALUE);
		int nHeight = userPrefs.getInt("frame_win_height", Integer.MAX_VALUE);
		if (nX == Integer.MAX_VALUE) {
			UIHelpers.centerScreen(this);
		} else
			setBounds(nX, nY, nWidth, nHeight);
		
		DisplayFloat.setRoundingPrefs(userPrefs.get("rounding", "").trim());

		// Setup to save window dimensions when it is closed
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				shutdown(); // remove cache files

				try {
					// Save the window dimensions to the preferences
					Preferences userPrefs = getPreferencesRoot();
					userPrefs.putInt("frame_win_x", getX());
					userPrefs.putInt("frame_win_y", getY());
					userPrefs.putInt("frame_win_width", getWidth());
					userPrefs.putInt("frame_win_height", getHeight());
					userPrefs.flush();

					// Halt if this is the only open frame
					--nFrames;
					if (nFrames == 0 && !theSettings.isApplet()) {
						cleanupMemory();
						System.exit(0);
					}
				} catch (Exception err) {
					ErrorReport.reportError(err, "Error during initialization");
				}
			}
		});

		// set title for the database chooser
		try {
			setTitle(title + " on " + 
				InetAddress.getLocalHost().getHostName());
		} catch (Exception e) {
			setTitle(title + " " + JPaveMain.JPAVE_VERSION );
		}
		tabbedPane = new HiddenTabbedPane();
		tabbedPane.setBorder(BorderFactory.createLineBorder(Color.GRAY));

		contentPane = getContentPane();
		contentPane.add(tabbedPane);

		++nFrames;
	}
	// CAS 12/22/14 all viewSingle panels call this to set the selected path 
	// so can be used on next file chooser
	public void setLastPath(String path) {
		try {
			int last = path.lastIndexOf("/");
			lastSaveFilePath = path.substring(0, last);
			if (!new File(lastSaveFilePath).isDirectory())
				lastSaveFilePath=System.getProperty("user.dir"); 
		}
		catch (Exception e) {ErrorReport.prtReport(e, "setting last path");}
	}
	/***********************************************************************
	 * XXX The Display All Assemblies window methods
	 ***********************************************************************/
	private void displayAssemblies() {
		try {
			// Close all tabs -- what is this?
			tabbedPane.removeAll();
			menuTree = null;

			// clear the selected assembly
			theSettings.setSelectedSession(null);
			selectedSession = null;

			// Add a tab for selecting the assembly to view
			Tab selectAssemblyTab = createAssemblySelectionTab(
					SessionData.getAssemblySessions(theSettings.getAllSessions()));
			
			tabbedPane.addTab("Select database", selectAssemblyTab);
		} catch (Exception err) {
			ErrorReport.reportError(err, "Problme displaying the sTCW databases");
			swapToExceptionTab(null, err);
		}
		pack();
	}
	
	 // Display the initial TCW assemblies window
	private Tab createAssemblySelectionTab(Vector<SessionData> inAssemblyList) {
	       Tab selectAssemblyTab = new Tab(this, null);

	       selectAssemblyTab.setBackground(bgColor);

	       // Build a sorted list of unique host names and unique PAVE database names
	       HostsCfg hosts = new HostsCfg();
	       Vector<String> hostList = new Vector<String>();
	       hostList.add(hosts.host);
	       
	       Vector<String> paveDBList = new Vector<String>();
	       
	       for (SessionData theSession : inAssemblyList) {
	           AssemblyData ad = theSession.getAssembly(0);
	           String hostName = ad.getDBData().getDB_URL();
	           if (!hostName.equalsIgnoreCase(hosts.host)) continue;
	           String paveDBName = ad.getDBData().getDBName();
	           
	           //if (!hostList.contains(hostName)) 	 hostList.add(hostName);
	           if (!paveDBList.contains(paveDBName)) paveDBList.add(paveDBName);
	       }
	       Collections.sort(hostList);
	       Collections.sort(paveDBList);
	       ArrayHelpers.removeDuplicates( paveDBList );

	       // Create a tree of the hosts
	       DefaultMutableTreeNode hostTree = new DefaultMutableTreeNode("");
           DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(hosts.host);
	       hostTree.add(newNode);
	       DefaultMutableTreeNode start = newNode;

	       // Add assembly nodes to host nodes
	       // The session only has a list of assemblies
	       // Create tree sorted on hosts, then paveDB names
	       // This is inefficient, but not enough items to matter
	       // Somewhere, theSession.toString is called for the [...] description on the PAVE assemblies
	       for (int i = 0; i < hostTree.getChildCount(); i++) {
	           DefaultMutableTreeNode hostNode = (DefaultMutableTreeNode) hostTree.getChildAt(i);
	           for (String dbname : paveDBList) {
	               for (SessionData theSession : inAssemblyList) {
	                   AssemblyData ad = theSession.getAssembly(0);
	                   String hostName = ad.getDBData().getDB_URL();
	                   String paveDBName = ad.getDBData().getDBName();
	                   
	                   if (hostName.equals(hostNode.toString()) && paveDBName.equals(dbname)) {
	                       hostNode.add(new DefaultMutableTreeNode(theSession));
	                   }
	               }
	           }
	       }

	       // Create display tree
	       DefaultTreeModel assemblyModel = new DefaultTreeModel(hostTree);
	       final JTree assemblyTree = new JTree(assemblyModel);
	       assemblyTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
	       assemblyTree.setRootVisible(false);
	       assemblyTree.setToggleClickCount(1);
	       ((DefaultTreeCellRenderer) assemblyTree.getCellRenderer()).setLeafIcon(null);
	       if (assemblyTree.getRowCount() == 1)
	           assemblyTree.expandRow(0);

	       assemblyTree.addMouseListener(new MouseAdapter() {
	           public void mouseClicked(MouseEvent e) {
	               TreePath path = assemblyTree.getSelectionPath();
	               if (path != null) {
	                   int depth = path.getPathCount();
	                   if (e.getClickCount() == 2 && depth >= 3) {
	                       DefaultMutableTreeNode node =
	                           (DefaultMutableTreeNode) assemblyTree
	                               .getLastSelectedPathComponent();
	                       doViewAssembly((SessionData) node.getUserObject());
	                   }
	               }
	           }
	       });

	       final JScrollPane assemblyScrollPane = new JScrollPane(assemblyTree);
	       assemblyScrollPane.setPreferredSize(new Dimension(400, 400));
	       Dimension dim = assemblyScrollPane.getMaximumSize();
	       assemblyScrollPane.setMaximumSize(new Dimension(Math.max(400,
	               (int) dim.getWidth()), Math.max(400, (int) dim.getHeight())));
	       assemblyScrollPane.setAlignmentX(Component.CENTER_ALIGNMENT);

	       JPanel buttonPanel = new JPanel();
	       buttonPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
	       buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.LINE_AXIS));
	       
	       final JButton btnViewAssembly = new JButton("Launch");
	       btnViewAssembly.setBackground(ManagerFrame.LAUNCHCOLOR);
	       btnViewAssembly.setEnabled(false);
	       final JButton btnGetState = new JButton("Overview");
	       btnGetState.setBackground(ManagerFrame.LAUNCHCOLOR);
	       btnGetState.setEnabled(false);
	       
	       btnViewAssembly.addActionListener(new ActionListener() {
	           public void actionPerformed(ActionEvent event) {
	               DefaultMutableTreeNode node = (DefaultMutableTreeNode)
	                   assemblyTree.getLastSelectedPathComponent();
	               doViewAssembly((SessionData) node.getUserObject());
	           }
	       });

	       assemblyTree.addTreeSelectionListener(new TreeSelectionListener() {
	           public void valueChanged(TreeSelectionEvent e) {
	               int depth = assemblyTree.getSelectionPath().getPathCount();
	               btnViewAssembly.setEnabled(depth >= 3);
	               btnGetState.setEnabled(depth >= 3);
	           }
	       });
	       
	       btnGetState.addActionListener(new ActionListener() {
	    	   public void actionPerformed(ActionEvent e) {
	               DefaultMutableTreeNode node = (DefaultMutableTreeNode)
                   assemblyTree.getLastSelectedPathComponent();
	               showStatePAVEDB((SessionData) node.getUserObject());
	    	   }
	       });
	       
	       final JButton btnExit = new JButton("Exit");
	       btnExit.addActionListener(new ActionListener() {
	    	   public void actionPerformed(ActionEvent arg0) {
	    		   WindowEvent wev = new WindowEvent(getInstance(), WindowEvent.WINDOW_CLOSING);
	    		   Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev);
	    	   }
	       });
	       
	       final JButton btnExitAll = new JButton("Exit All");
	       btnExitAll.addActionListener(new ActionListener() {
	    	   public void actionPerformed(ActionEvent e) {
	    		   if(openFrames != null) {
	    			   for(int x=0; x<openFrames.size(); x++) {
	    	    		   WindowEvent wev = new WindowEvent(openFrames.get(x), WindowEvent.WINDOW_CLOSING);
	    	    		   Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev);
	    			   }
	    		   }
	    	   }
	       });

	       // Select current host in list
	       if (start != null) {
	           TreeNode[] nodes = assemblyModel.getPathToRoot(start.getFirstLeaf());
	           TreePath path = new TreePath(nodes);
	           assemblyTree.scrollPathToVisible(path);
	           assemblyTree.setSelectionPath(path);
	       }

	       buttonPanel.add(btnViewAssembly);
	       buttonPanel.add(Box.createHorizontalStrut(10));
	       buttonPanel.add(btnGetState);
	       buttonPanel.add(Box.createHorizontalStrut(10));
	       buttonPanel.add(btnExit);
	       buttonPanel.add(Box.createHorizontalStrut(10));
	       buttonPanel.add(btnExitAll);
	       
	       buttonPanel.setMaximumSize(buttonPanel.getPreferredSize());
	       buttonPanel.setMinimumSize(buttonPanel.getPreferredSize());
	       
	       selectAssemblyTab.setLayout(new BoxLayout(selectAssemblyTab,
	               BoxLayout.Y_AXIS));
	       selectAssemblyTab.add(Box.createVerticalStrut(20));
	       selectAssemblyTab.add(UIHelpers.createCenteredLabel("singleTCW Databases"));
	       selectAssemblyTab.add(assemblyScrollPane);
	       selectAssemblyTab.add(Box.createVerticalStrut(5));
	       selectAssemblyTab.add(buttonPanel);
	       selectAssemblyTab.add(Box.createVerticalStrut(20));

	       return selectAssemblyTab;
	   }

	/**************************************************************
	 * XXX Show overview
	 */
	private void showStatePAVEDB(final SessionData session) {
		try {

			String val = null;
			
			AssemblyData assmData = session.getAssembly(0);
			DatabaseData data = assmData.getDBData();	
			
			if(!DBConn.checkMysqlDB("viewSingle ", data.getDB_URL(), data.getDBName(), 
					data.getQueryUser(), data.getQueryPassword())) {
				val = "Database not available";
				UserPrompt.displayInfoMonoSpace(null, "Overview for " + data.getDBName(), val.split("\n"), false, true);
				return;
			}
	
			DBConn conn = new DBConn(data.getDB_URL() ,data.getDBName(), data.getQueryUser(), data.getQueryPassword());
			ResultSet rset = conn.executeQuery("SELECT pja_msg FROM assem_msg");
			// update overview
			if(rset.first()) {
				val = rset.getString(1);
				if (val == null || val.length() <= 5) {	
					Vector<String> lines = new Vector<String>();
					session.overview(lines);
					val="";
					for (int i=0; i<lines.size(); i++) 
						val += lines.get(i) + "\n";
				}
			}
			rset.close();
			UserPrompt.displayInfoMonoSpace(null, "Overview for " + data.getDBName(), val.split("\n"), false, true);
		}
		catch(Exception e) {
			ErrorReport.prtReport(e, "Error getting project state");
		}
	}

	/****************************************************************
	 * XXX doViewAssembly
	 */
	private void doViewAssembly(final SessionData session)  {

		if (!goodVersion(session)) return;
		
		final JFrame loadFrame = new JFrame();
		final Thread theThread = new Thread(new Runnable() {
			public void run() {				
				Container pane = loadFrame.getContentPane();
				
				//loadFrame.setTitle(title + " " + JPaveMain.JPAVE_VERSION);
	
				pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));

				JLabel loadLabel = new JLabel("Loading \""
						+ session.getDescription() + "\" ...", JLabel.CENTER);
				loadLabel.setAlignmentX(Component.CENTER_ALIGNMENT);

				JProgressBar progressBar = new JProgressBar();
				progressBar.setMaximumSize(new Dimension(200, 20));
				progressBar.setStringPainted(true);
				progressBar.setString("");
				progressBar.setIndeterminate(true);
				progressBar.setAlignmentX(Component.CENTER_ALIGNMENT);

				loadFrame.add(Box.createVerticalGlue());
				loadFrame.add(loadLabel);
				loadFrame.add(progressBar);
				loadFrame.add(Box.createVerticalGlue());

				loadFrame.setSize(350, 150);
				loadFrame.setVisible(true);

				JPaveFrame.setupUIDefaults();
				JPaveFrame frame = new JPaveFrame(theSettings);
				frame.openAssembly(null, session);

				if (loadFrame.isShowing())
					frame.setVisible(true);
				else
					// canceled: user closed progress loading frame
					frame.dispose();

				loadFrame.dispose();
			}
		});

		loadFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		loadFrame.addWindowListener(new WindowAdapter() {
			public void windowClosed(WindowEvent e) {
				theThread.interrupt(); // kill the assembly load - not working!
										// is it because of child threads?
			}
		});
		theThread.start(); // start the assembly load
	}	
	
	/********************************************************************
	 * XXX  Single assembly window
	 * Creates the left panel of menu items and the outer left panel
	 * Reads the assembly and displays the overview
	 */
	private void openAssembly(Component oldTab, SessionData newSelection) {
		try {
			selectedSession = newSelection;
			theSettings.setSelectedSession(newSelection);

			// set title for viewing an assembly
			setTitle(title + " " + JPaveMain.JPAVE_VERSION + ":   " + newSelection.getViewDescription());
			System.err.println("   Open database");

			//Load the library counts
			AssemblyData assmData = selectedSession.getAssembly(0);
			DatabaseData dbData = assmData.getDBData();		
			Connection conn = dbData.createDBConnection();
			
			if (!setFlags(conn)) 
				System.err.print("Warning: problem setting state");
			
			//Create root of menu system
			MenuTreeNode root = new MenuTreeNode();

			Tab instructionsTab = new StyleTextTab(this, null,HTML);
			tabbedPane.addTab(Instructions, instructionsTab);

			// Creates Overview and set flags in JPaveDBWrapper, e.g. hasPairWise()
			// it is threaded so can't depend on flags for initial left panel
			Tab overviewTab = addOverviewTab(); 
			tabbedPane.addTab(Overview, overviewTab);

			System.out.println("   Build interface");
			queryTab = new QueryTab(this, new QueryData(QueryData.QUERY_CONTIGS, theSettings, nFrameID));
			tabbedPane.addTab("Query", queryTab); // used for Contig and Pairs Query
		
			//Link  to the query interface
			MenuTreeNode seqFilterNode = new MenuTreeNode(FilterContigsQuery, queryTab);
			MenuTreeNode pairFilterNode = new MenuTreeNode(FilterPairsQuery, queryTab);
			queryTab.setupDialog(seqFilterNode, pairFilterNode);
			basicContigQueryTab = new BasicQueryTab(this, BasicQueryTab.VIEW_BY_SEQ, new BasicQueryData(theSettings));
			tabbedPane.addTab(BasicContigQuery, basicContigQueryTab);
			
			basicHitQueryTab = new BasicQueryTab(this, BasicQueryTab.VIEW_BY_HIT, new BasicQueryData(theSettings));
			tabbedPane.addTab(BasicHitQuery, basicHitQueryTab);
			
			if(bHasGOs) {
				basicGOQueryTab = new BasicGOQueryTab(this, new BasicQueryData(theSettings));
				tabbedPane.addTab(BasicGOQuery, basicGOQueryTab);
			}
			
			MenuTreeNode basicContigQueryNode = new MenuTreeNode(BasicContigQuery, basicContigQueryTab);
			MenuTreeNode basicHitQueryNode = new MenuTreeNode(BasicHitQuery, basicHitQueryTab);
			MenuTreeNode basicGOQueryNode = null;
			
			if(bHasGOs)
				basicGOQueryNode = new MenuTreeNode(BasicGOQuery, basicGOQueryTab);

			blastTab = new BlastTab(this); 
			tabbedPane.addTab(Blast, blastTab);
			MenuTreeNode blastNode = new MenuTreeNode(Blast,blastTab);
			
			//Setup the columns for Contigs and Pairs
			MenuTreeNode ctgColumnNode = new MenuTreeNode(SelectContigsColumns);
			MenuTreeNode pairColumnNode = new MenuTreeNode(SelectPairsColumns);
			
			fieldTab = new FieldTab(this, ctgColumnNode, pairColumnNode); 
			ctgColumnNode.setUserObject(fieldTab);
			pairColumnNode.setUserObject(fieldTab);
			tabbedPane.addTab(SelectContigsColumns, fieldTab);
			tabbedPane.addTab(SelectPairsColumns, fieldTab);
			
			fieldTab.updateOnStartup();
			//Columns for displaying query criteria on the "List Contigs Queries" 
			String [] colNames = new String [] { 	QueryTab.GENERAL_HEADER, QueryTab.LIBRARY_HEADER,
										QueryTab.BEST_DB_HIT_HEADER };
			
			resultsContigTab = new ResultsSummaryTab(this, null, colNames);
			tabbedPane.addTab(ListContigsQueries, resultsContigTab);
			
			//Columns for displaying queries on the "List Pairs Queries"
			colNames = new String [] { "Description", "Similarity"};
			resultsPairTab = new ResultsSummaryTab(this, null, colNames);
			tabbedPane.addTab("Query Pair Results", resultsPairTab);

			/*
			 *  Display on left panel
			 */
			MenuTreeNode general = new MenuTreeNode(GeneralSection);
			general.addChild(new MenuTreeNode(Instructions, instructionsTab));
			general.addChild(new MenuTreeNode(Overview, overviewTab));
			root.addChild(general);

			MenuTreeNode contigHeader = new MenuTreeNode(SequenceSection);
			contigHeader.addChild(new MenuTreeNode(ShowAllContigs));
			contigHeader.addChild(ctgColumnNode);
			contigHeader.addChild(seqFilterNode);
			contigHeader.addChild(new MenuTreeNode(ListContigsQueries, resultsContigTab));
			root.addChild(contigHeader);	
			seqFilterNode.collapseChildren();

			MenuTreeNode basicHeader = new MenuTreeNode(BasicSection);
			basicHeader.addChild(basicContigQueryNode);
			basicHeader.addChild(basicHitQueryNode);
			if(bHasGOs)
				basicHeader.addChild(basicGOQueryNode);
			basicHeader.addChild(blastNode);
			root.addChild(basicHeader);
			
			MenuTreeNode pairHeader = new MenuTreeNode(PairsSection);
			pairHeader.addChild(new MenuTreeNode(ShowAllPairs));
			pairHeader.addChild(pairColumnNode);
			pairHeader.addChild(pairFilterNode);
			pairHeader.addChild(new MenuTreeNode(ListPairsQueries, resultsPairTab));
			root.addChild(pairHeader);			
			pairFilterNode.collapseChildren();

			menuTree = new MenuTree(root);
			menuTree.addMenuTreeNodeListener(menuTreeListener);
			
			handleLoadQuery(null, 
					QueryData.createAllContigsQuery(theSettings, nFrameID), 
					null);

			handleLoadQuery(null, 
					QueryData.createAllContigPairsQuery(theSettings, nFrameID),
					null);

			setSize(1024, 768);

			menuTree.getNodeWithName(ShowAllContigs).setUserObject(
					tabbedPane.getTabWithTitle(ShowAllContigs));
			menuTree.getNodeWithName(ShowAllPairs).setUserObject(
					tabbedPane.getTabWithTitle(ShowAllPairs));
			
			if (!JPaveDBWrapper.hasPairWise(conn)) { 
				menuTree.getNodeWithName(FilterPairsQuery).hideChildren();
				menuTree.getNodeWithName(PairsSection).setVisible(false);
				menuTree.getNodeWithName(FilterPairsQuery).setVisible(false);
				menuTree.getNodeWithName(ShowAllPairs).setVisible(false);
				menuTree.getNodeWithName(ListPairsQueries).setVisible(false);
				menuTree.getNodeWithName(SelectPairsColumns).setVisible(false);
			}				
			
			JScrollPane menuScrollPane = new JScrollPane(menuTree);
			menuScrollPane.setPreferredSize(new Dimension(400, 400));
			menuScrollPane.getVerticalScrollBar().setUnitIncrement(10);
			menuScrollPane.setBackground(bgColorLeft);
			
			/*
			 * Buttons in top panel for different options from left panel
			 * Other buttons are in ContigListTab.java and ContigPairListTab.java
			 */
			JPanel buttonPanel = new JPanel();
					// next statement doesn't seem to effect anything
			buttonPanel.setBorder(BorderFactory.createLineBorder(Color.WHITE)); 
			buttonPanel.setBackground(bgColor);

			if (lblStatus == null) {
				lblStatus = new JLabel("Working...");
				lblStatus.setForeground(bgColor);
			}

			// For Basic Contigs, Filter Contigs, and Filter Pairs, 
			// this gets executed when the "Show ..." button at the top is executed
			btnExecute = new JButton(ShowFilteredContigs);
			btnExecute.setBackground(ManagerFrame.FUNCTIONCOLOR);
			btnExecute.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent event) {

					if(btnExecute.getText().equals(ShowFilteredContigs)){
						queryTab.executeQuery(true);
					}
					else if(btnExecute.getText().equals(ShowFilteredPairs)) {
						queryTab.executeQuery(false);
					}
				}
			});
			
			// Top left Defaults button
			btnDefaults = new JButton(RestoreDefaults);
			btnDefaults.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					// Set query options defaults				
					QueryData query = new QueryData(QueryData.QUERY_CONTIGS, theSettings, nFrameID);

					queryTab.setUIFromFilter(query);
					queryTab.restoreLibraryDefaults();
					queryTab.restoreAnnoDefaults();
					
					// Set column defaults
					try {
						FieldMapper ctgMap = FilterContigData.createContigFieldMapper(getInstance());
						fieldTab.setContigUIFromFields(ctgMap);
						fieldTab.resetDynamicLists();
					} catch (Exception e1) {
						// always get this error, but everything seems to work
						ErrorReport.reportError(e1, "JPaveFrame creating columns");
					}
					FieldMapper pairMap = QueryData.createContigPairFieldMapper();
					fieldTab.setContigPairUIFromFields(pairMap);
				}
			});

			JPanel leftPanel = new JPanel();
			
			leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.PAGE_AXIS));
			btnDefaults.setAlignmentX(CENTER_ALIGNMENT);
			leftPanel.setBackground(bgColorLeft); // only effects upper part
			leftPanel.add(Box.createVerticalStrut(5));
			leftPanel.add(btnDefaults);
			leftPanel.add(Box.createVerticalStrut(5));
			leftPanel.add(menuScrollPane);

			buttonPanel.add(btnExecute);
			buttonPanel.add(lblStatus);

			viewPane = new JPanel();
			viewPane.setLayout(new BoxLayout(viewPane, BoxLayout.Y_AXIS));
			viewPane.add(buttonPanel);
			viewPane.add(tabbedPane);

			splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
					leftPanel, viewPane);
			splitPane.setOneTouchExpandable(true);
			splitPane.setDividerLocation(215);

			contentPane.add(splitPane);
			
			tabbedPane.setSelectedTab(overviewTab);
			menuTree.setSelectedNode("Overview");
		} catch (Exception err) {
			ErrorReport.reportError(err, "Internal error: open assembly 2");
			swapToExceptionTab(null, err);
		}
	}
	
	private JPaveFrame getInstance() { return this; }

	/********************************************************
	 * Selecting an item in the menu tree, or adding an item to the menu tree
	 * evokes this listener. However, next and prev do not evoke this.
	 */
	private MenuTreeNodeListener menuTreeListener = new MenuTreeNodeListener() {
		public void eventOccurred(MenuTreeNodeEvent e) {
			MenuTreeNode node = e.getNode();
			if (node == null)return;

			Tab tab = (Tab) node.getUserObject();
			if (tab == null) return;

			setButtonsVisible(node);
			
			if(btnExecute!=null)
			{
				if(node.getText().equals(BasicContigQuery) || node.getText().equals(BasicHitQuery) || node.getText().equals(BasicGOQuery))
					btnExecute.setVisible(false);
				else if((node.getParentNode() != null && node.getParentNode().getText().equals("Similar Sequence Pairs"))) {
					btnExecute.setVisible(true);
					btnExecute.setText(ShowFilteredPairs);
				}
				else {
					btnExecute.setVisible(true);
					btnExecute.setText(ShowFilteredContigs);
				}
			}
			
			if (e.getType() == MenuTreeNodeEvent.TYPE_CLOSED) {
				//if tab was generated by CAP3 or PHRAP, delete temp file
				CAP3orPhrapTab.RemoveResult(tab.getID());
				MenuTreeNode parentNode = node.getParentNode();
				menuTree.removeNode(node);
				tabbedPane.remove(tab);
				resultsContigTab.removeResultSummary(tab);
				resultsPairTab.removeResultSummary(tab);
				menuTree.setSelected(parentNode);
				tab = (Tab) parentNode.getUserObject();
				tabbedPane.setSelectedTab(tab);
			} else if (e.getType() == MenuTreeNodeEvent.TYPE_SELECTED)
				tabbedPane.setSelectedTab(tab);

			if (tab instanceof QueryTab) 
				((QueryTab) tab).filterWindow(node);
			else if (tab instanceof FieldTab) 
				((FieldTab) tab).zoom(node);
		}
	};
	private void setButtonsVisible(MenuTreeNode node)
	{
		if(node == null) return;
		if((node.getText().equals(FilterContigsQuery) || 
			node.getText().equals(FilterPairsQuery)) ||
				(node.getParentNode() != null && 
				(node.getParentNode().getText().equals(FilterContigsQuery) || 
				node.getParentNode().getText().equals(FilterPairsQuery))))
		{
			if(btnExecute != null) btnExecute.getParent().setVisible(true);
		}
		else if(node.getText().equals(SelectContigsColumns) ||
				node.getText().equals(SelectPairsColumns)) {
			if(btnExecute != null) btnExecute.getParent().setVisible(true);
			
		}
		else
		{
			if(btnExecute != null) btnExecute.getParent().setVisible(false);
		}
	}
	
	/************************************************************************
	 * XXX get information about project
	 */
	private boolean setFlags (Connection conn) {
		bHasNs = JPaveDBWrapper.hasNs(conn);
		bHasContigSets = JPaveDBWrapper.hasContigSet(conn);
		bHasExpLevels = JPaveDBWrapper.hasLibs(conn);
		bHasGOs = Version.doesTableExist(conn, "pja_unitrans_go");
		bHasAssembly = !JPaveDBWrapper.hasNoAssembly(conn);
		bHasKEGG = JPaveDBWrapper.hasKEGG(conn);
		bHasPFAM = JPaveDBWrapper.hasPFAM(conn);
		bHasEC = JPaveDBWrapper.hasEC(conn);
		nContigs = JPaveDBWrapper.nContigs(conn);
		bHasBuried = JPaveDBWrapper.hasBuried(conn);
		bHasMatePairs = JPaveDBWrapper.hasMatePairs(conn);
		bHasPairwise = JPaveDBWrapper.hasPairWise(conn);
		bIsProteinDB = JPaveDBWrapper.isProteinDB(conn);
		bHasDBhits = JPaveDBWrapper.hasDBhits(conn);
		nContigSets = JPaveDBWrapper.nContigSets(conn);
		bHasSelfblast = JPaveDBWrapper.hasSelfblast(conn);
		bHasTselfblast = JPaveDBWrapper.hasTselfblast(conn);
		bHasLoc = JPaveDBWrapper.hasLoc(conn);
		return true;
	}
	public boolean hasContigSets() { return bHasContigSets; }
	public boolean hasKEGG() { return bHasKEGG; }
	public boolean hasPFAM() { return bHasPFAM; }
	public boolean hasEC() { return bHasEC; }
	public boolean hasGOs() { return bHasGOs; }
	public boolean hasNs() { return bHasNs; }
	public boolean hasExpLevels() { return bHasExpLevels; }
	public boolean hasAssembly() { return bHasAssembly; }
	public boolean hasNoAssembly() { return !bHasAssembly;}
	public int nContigs () {return nContigs; }
	public boolean hasBuried() {return bHasBuried; }
	public boolean hasMatePairs() {return bHasMatePairs;}
	public boolean hasPairWise() {return bHasPairwise;}
	public boolean hasSelfblast() {return bHasSelfblast;}
	public boolean hasTselfblast() {return bHasTselfblast;}
	public boolean isProteinDB () {return bIsProteinDB;}
	public boolean isNucleoDB() {return !bIsProteinDB;}
	public int nContigSets() {return nContigSets;}
	public boolean hasDBhits() { return bHasDBhits;}
	public boolean hasLoc() { return bHasLoc;}
	
	
	public String [] getAnnoDBs() {
		String [] retVal = null;
		try {
			AssemblyData assmData = selectedSession.getAssembly(0);
			DatabaseData dbData = assmData.getDBData();		
			Connection conn = dbData.createDBConnection();
			retVal = JPaveDBWrapper.getAnnoTypes(conn, assmData.getAssemblyID());
			conn.close();
		} catch(Exception e) {
			ErrorReport.reportError(e);
		}
		return retVal;
	}
	
	public Vector<String> getSpecies() {
		Vector<String> retVal = null;
		try {
			AssemblyData assmData = selectedSession.getAssembly(0);
			DatabaseData dbData = assmData.getDBData();		
			Connection conn = dbData.createDBConnection();
			retVal = JPaveDBWrapper.getSpecies(conn, assmData.getAssemblyID());
			conn.close();
		} catch(Exception e) {
			ErrorReport.reportError(e);
		}
		return retVal;
	}
	
	public GlobalSettings getSettings() {return theSettings;}
	
	//Load individual contigs for a table
	public void loadContigs(String tag, String [] contigs, int viewMode) {
		queryTab.executeQuery(tag, contigs, viewMode);
	}
	
	public void setButtonsVisible(boolean show) {
		if(btnExecute != null) btnExecute.getParent().setVisible(show);
	}
	
	public void setEnableExecute(boolean enable) {
		if(btnExecute != null) btnExecute.setEnabled(enable);
	}
	
	public static JPaveFrame getParentFrame(int frameID) {
		return theFramesList.getFrame(frameID);
	}
	
	public int getFrameID() { return nFrameID; }

	//Adds contig list table from basic query
	public void addQueryResultsTab(QueryData theQuery, String [] contigIDs, int viewMode, String strTabID) {
		handleLoadQuery(null, theQuery, contigIDs, viewMode, strTabID);
	}
	
	// called in QueryTab
	public void addQueryResultsTab(QueryData theQuery, String strTabID) 
	{
		handleLoadQuery(null, theQuery, strTabID);
	}
	
	// called in BasicQueryTab
	public void addBasicQueryResultsTab(String [] theContigs, 
			BasicQueryData theQuery, String strTabID) 
	{
		handleLoadQuery(null, theQuery, theContigs, strTabID);
	}
	
	private FieldMapper getFieldsForQuery()
	{
		return getContigListFields(true);
	}

	private FieldMapper getFieldsForQuery(QueryData theQuery) 
	{
		FieldMapper fields = null;
		if (theQuery.getType() == QueryData.QUERY_CONTIGS)
			fields = getContigListFields(true);
		else
			fields = getContigPairListFields();
		return fields;
	}
	
	// called by ContigGOPanel
	public ResultSet doQuery(String query) {
		try {
			AssemblyData assmData = selectedSession.getAssembly(0);
			DatabaseData dbData = assmData.getDBData();		
			Connection conn = dbData.createDBConnection();
			
			Statement stmt = conn.createStatement( 
					ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY );

			ResultSet rset = stmt.executeQuery(query);
			return rset;
		} catch(Exception e) {
			ErrorReport.reportError(e);
		}
		return null;
	}
	// handleLoadQuery for BasicQueryData
	public void handleLoadQuery(Tab oldTab, BasicQueryData inQuery, String [] contigs,
			final String strTabID) 
	{
		if(contigs!=null)
			inQuery.setContigs(contigs);
		
		try {
			// Make a deep copy of the query object to save in the tab
			final BasicQueryData theQuery = (BasicQueryData) Converters.deepCopy(inQuery);
			final String [] theContigs = inQuery.getContigs();
			
			String strTabTitle = strTabID;

			// Setup a progress dialog
			final String strCacheFileName = GlobalSettings.getJPaveTempPath()
					+ "/" + strUniqueID + "_" + strTabTitle + ".cache";
			
			final InterThreadProgress progress = new InterThreadProgress(this);
			progress.swapInTabAndStartThread(oldTab, strTabTitle,
				new InterThreadProgress.WorkerThread() {
					public void run() throws Throwable {
						progress.setProgressLabel("Loading ...");
	
						// Add new node to menu tree
						String nodeID = strTabID;
						MenuTreeNode newNode = new MenuTreeNode(nodeID);
						// tab doesn't already exist, e.g. not "Clones", etc
						if (strTabID != null) { 
							newNode.setUserObject(progress.getProgressPanel());
							menuTree.addNode(ListContigsQueries, newNode);
							// must come after addNode
							newNode.setType(MenuTreeNode.TYPE_CLOSEABLE); 
							menuTree.setSelected(newNode);
						}

						// Get the user preferences for displayed fields...
						FieldMapper fields = getFieldsForQuery();
						
						// Execute the query...
						int count = theQuery.loadQueryResults(
								theContigs, selectedSession, fields, fieldTab, strCacheFileName);
						Tab newTab = null;

						newTab = new ContigListTab(JPaveFrame.this,
										fields, theQuery,
										strCacheFileName, nContigs, true, BASIC_QUERY_MODE_HIT);

						progress.swapOutProgress(newTab);
						// tab doesn't already exist, e.g. "Clones", etc
						if (strTabID != null) { 
							// Update new node with new tab of query results
							if (!progress.wasCanceled()) {
								nodeID = strTabID + ": " + count + " contigs";
								newNode.setText(nodeID);
								newNode.setUserObject(newTab);
								// Add new entry to results summary table
								resultsContigTab.addBasicResultSummary(nodeID, newTab, newNode);
							} else
								// this thread was canceled but hasn't been killed yet									
								newNode.setUserObject(progress.getCancelPanel());
							menuTree.setSelected(newNode);
						}
					}
				});
		} catch (Exception err) {
			ErrorReport.reportError(err, "Internal error: loading query");
		}
	}
	
	// Used for displaying individual contigs from the basic query (Could be combined with the filter handleLoadQuery
	public void handleLoadQuery(Tab oldTab, QueryData inQuery, final String [] contigIDs, 
	            final int viewMode, final String strTabID) { // strTabID shows on left followed by ':' # seqs
		try {
			final QueryData theQuery = (QueryData) Converters.deepCopy(inQuery);

			// Figure out how the results should be displayed
			String strTabTitle = strTabID;
			if (oldTab != null)
				strTabTitle = oldTab.getTitle();

			// Setup a progress dialog
			String strCacheTag = strTabTitle.replaceAll(":", ""); // can't use :: for the GOs on Windows
			final String strCacheFileName = GlobalSettings.getJPaveTempPath()
					+ "/" + strUniqueID + "_" + strCacheTag + ".cache";
			final InterThreadProgress progress = new InterThreadProgress(this);
			
			progress.swapInTabAndStartThread(oldTab, strTabTitle,
					new InterThreadProgress.WorkerThread() 
			{
				public void run() throws Throwable 
				{
					progress.setProgressLabel("Loading ...");

					String nodeID = strTabID;
					MenuTreeNode newNode = new MenuTreeNode(nodeID);
					
					if (strTabID != null) { 
						newNode.setUserObject(progress.getProgressPanel());
						
						if(viewMode == BASIC_QUERY_MODE_UNITRANS)
							menuTree.addNode(BasicContigQuery, newNode);
						else if(viewMode == BASIC_QUERY_MODE_HIT)
							menuTree.addNode(BasicHitQuery, newNode);
						else
							menuTree.addNode(BasicGOQuery, newNode);
						
						// must come after addNode
						newNode.setType(MenuTreeNode.TYPE_CLOSEABLE); 
						menuTree.setSelected(newNode);
					}

					// Get the user preferences for displayed fields...
					FieldMapper fields = getFieldsForQuery(theQuery);
					
					// XXX Execute the query...
					int count = theQuery.loadQueryResults(
							selectedSession, fieldTab, fields, contigIDs, strCacheFileName);
					Tab newTab = null;
					// Setup the output tab
					if (theQuery.getType() == QueryData.QUERY_CONTIGS) {
						newTab = new ContigListTab(JPaveFrame.this,
								fields, theQuery, contigIDs, strCacheFileName, nContigs);
					}
					else {
						newTab = new ContigPairListTab(JPaveFrame.this,
								fields, theQuery, strCacheFileName);
					}

					progress.swapOutProgress(newTab);
					if (strTabID != null) { 
						// Update new node with new tab of query results
						if (!progress.wasCanceled()) {
							nodeID = strTabID + ": " + count + " seqs";
							newNode.setText(nodeID);
							newNode.setUserObject(newTab);
							// Add new entry to results summary table
							if(theQuery.getType() == QueryData.QUERY_CONTIGS)
								resultsContigTab.addResultSummary(nodeID,newTab, newNode);
							else
								resultsPairTab.addResultSummary(nodeID,newTab, newNode);
						} else {
							// this thread was canceled but hasn't been killed yet
							newNode.setUserObject(progress.getCancelPanel());
						}
						menuTree.setSelected(newNode);
					}
				}
			});
		} catch (Exception err) {
			ErrorReport.reportError(err, "Internal error: loading query");
		}
	}

	// handle Query for Contig or Pairs data
	public void handleLoadQuery(Tab oldTab, QueryData inQuery, final String strTabID) {
		try {
			final QueryData theQuery = (QueryData) Converters.deepCopy(inQuery);

			// Figure out how the results should be displayed
			String strTabTitle = strTabID;
			if (oldTab != null)
				strTabTitle = oldTab.getTitle();
			else if (strTabID == null && theQuery.isAllContigs()) {				
				// A null query; i.e. display all contigs
				oldTab = tabbedPane.getTabWithTitle(ShowAllContigs);
				if (oldTab != null) {
					FieldMapper newFields = getContigListFields(true);
					FieldMapper oldFields = ((ContigListTab) oldTab).getFieldMapper();

					if (oldFields.hasSameVisibleFields(newFields)) {
						// Already displayed, just select the tab
						tabbedPane.setSelectedTab(oldTab);
						menuTree.setSelectedNode(ShowAllContigs);
						return;
					}
				}
				strTabTitle = ShowAllContigs;
			} else if (strTabID == null && theQuery.isAllContigPairs()) {
				oldTab = tabbedPane.getTabWithTitle(ShowAllPairs);
				if (oldTab != null) {
					FieldMapper newFields = getContigPairListFields();
					FieldMapper oldFields = ((ContigPairListTab) oldTab).getFieldMapper();

					if (oldFields.hasSameVisibleFields(newFields)) {
						// Already displayed, just select the tab
						tabbedPane.setSelectedTab(oldTab);
						menuTree.setSelectedNode(ShowAllPairs);
						return;
					}
				}
				strTabTitle = ShowAllPairs;
			} 

			// Setup a progress dialog
			final String strCacheFileName = GlobalSettings.getJPaveTempPath()
					+ "/" + strUniqueID + "_" + strTabTitle + ".cache";
			final InterThreadProgress progress = new InterThreadProgress(this);
			
			progress.swapInTabAndStartThread(oldTab, strTabTitle,
					new InterThreadProgress.WorkerThread() 
			{
				public void run() throws Throwable 
				{
					progress.setProgressLabel("Loading ...");

					String nodeID = strTabID;
					MenuTreeNode newNode = new MenuTreeNode(nodeID);
					
					if (strTabID != null) { 
						newNode.setUserObject(progress.getProgressPanel());
						
						if(theQuery.getType() == QueryData.QUERY_CONTIGS)
							menuTree.addNode(ListContigsQueries, newNode);
						else
							menuTree.addNode(ListPairsQueries, newNode);
						
						// must come after addNode
						newNode.setType(MenuTreeNode.TYPE_CLOSEABLE); 
						menuTree.setSelected(newNode);
					}

					// Get the user preferences for displayed fields...
					FieldMapper fields = getFieldsForQuery(theQuery);
					
					// XXX Execute the query...
					int count = theQuery.loadQueryResults(
							selectedSession, fieldTab, fields, strCacheFileName);
					Tab newTab = null;
					// Setup the output tab
					if (theQuery.getType() == QueryData.QUERY_CONTIGS) {
						newTab = new ContigListTab(JPaveFrame.this,
								fields, theQuery, null, strCacheFileName, nContigs);
					}
					else {
						newTab = new ContigPairListTab(JPaveFrame.this,
								fields, theQuery, strCacheFileName);
					}

					progress.swapOutProgress(newTab);
					if (strTabID != null) { 
						// Update new node with new tab of query results
						if (!progress.wasCanceled()) {
							nodeID = strTabID + ": " + count + " seqs";
							newNode.setText(nodeID);
							newNode.setUserObject(newTab);
							// Add new entry to results summary table
							if(theQuery.getType() == QueryData.QUERY_CONTIGS)
								resultsContigTab.addResultSummary(nodeID,newTab, newNode);
							else
								resultsPairTab.addResultSummary(nodeID,newTab, newNode);
						} else {
							// this thread was canceled but hasn't been killed yet
							newNode.setUserObject(progress.getCancelPanel());
						}
						menuTree.setSelected(newNode);
					}
				}
			});
		} catch (Exception err) {
			ErrorReport.reportError(err, "Internal error: loading query");
		}
	}

	private static int nThreads = 0;

	public void updateStatus(int action) 
	{
		if (action == 1) // thread started
			nThreads++;
		else if (action == 0) // thread ended
			nThreads--;

		if (lblStatus == null)
			lblStatus = new JLabel("Working...");

		if (nThreads > 0)
			lblStatus.setForeground(Color.BLACK); // show
		else
			lblStatus.setForeground(bgColor); // hide
	}

	// JPaveFrame.openAssembly creates FieldTab, which then calls this
	// routine in order to call FilterContigData.createContigFieldMapper
	public FieldMapper getContigListFields( boolean forQuery) 
	{
		FieldMapper fields;
		try {
			fields = FilterContigData.createContigFieldMapper(this);
			
		} catch (Exception e) {
			ErrorReport.reportError(e, "JPaveFrame: could not get contig list");
			return null;
		}
		String strList = getPreferencesRoot().get(ctgPrefID(), "");

		if(!forQuery)
			fields.setVisibleFieldIDsList(strList.split(","));
		else
			fields.setVisibleFieldIDsDB(strList.split(","));
		return fields;
	}

	public FieldMapper getContigPairListFields() 
	{
		FieldMapper fields = QueryData.createContigPairFieldMapper();
		String strList = getPreferencesRoot().get(pairPrefID(), "");
		fields.setVisibleFieldIDsList(strList.split(","));
		return fields;
	}
	/******************************************************************************
	 * XXX Preferences -- prefixed by database name
	 * these are also in FieldTab
	 */
	private String ctgPrefID() {
		String sTCWdb = getAssemblyList().getAssembly(0).getDBName();
		return sTCWdb + "_ctgID";
	}
	
	private String pairPrefID() {
		String sTCWdb = getAssemblyList().getAssembly(0).getDBName();
		return sTCWdb + "_pairID";
	}

	// adds a tab on the left under Show All Contigs
	public Tab addContigPage(String strContigName, Tab parentTab, int nRecordNum, String location) 
	{
		Tab tab = addNewContigTab(strContigName, parentTab, nRecordNum, null);
		MenuTreeNode newNode = new MenuTreeNode(strContigName);
		tab.setMenuNode(newNode);
		newNode.setUserObject(tab);
		menuTree.addNode(location, newNode);
		newNode.setType(MenuTreeNode.TYPE_CLOSEABLE);
		menuTree.setSelected(newNode);
		
		return tab;
	}
	
	public void addBasicClusterPage(MainData theCluster, String strTitle,
			Tab parentTab, int nRecordNum) 
	{
		Tab tab = addNewClusterTab(theCluster, parentTab, nRecordNum, strTitle, null);
		
		MenuTreeNode newNode = new MenuTreeNode(strTitle);
		tab.setMenuNode(newNode);
		newNode.setUserObject(tab);
		menuTree.addNode(ListContigsQueries, newNode);
		resultsContigTab.addBasicResultSummary(strTitle, tab, newNode);
		newNode.setType(MenuTreeNode.TYPE_CLOSEABLE);
		menuTree.setSelected(newNode);
	}

	public void addClusterPage(MainData theCluster, String strTitle,
			Tab parentTab, int nRecordNum) 
	{
		Tab tab = addNewClusterTab(theCluster, parentTab, nRecordNum, strTitle, null);
		
		MenuTreeNode newNode = new MenuTreeNode(strTitle);
		tab.setMenuNode(newNode);
		newNode.setUserObject(tab);
		if (parentTab instanceof ContigPairListTab)
			menuTree.addNode(ShowAllPairs, newNode);
		else if(parentTab instanceof BasicQueryTab) {
			if(((BasicQueryTab)parentTab).getType() == BasicQueryTab.VIEW_BY_SEQ)
				menuTree.addNode(BasicContigQuery, newNode);
			else //if(((BasicQueryTab)parentTab).getType() == BasicQueryTab.TYPE_BASIC_HIT)
				menuTree.addNode(BasicHitQuery, newNode);
		}
		else
			menuTree.addNode(ShowAllContigs, newNode);
		newNode.setType(MenuTreeNode.TYPE_CLOSEABLE);
		menuTree.setSelected(newNode);
	}

	public Tab addNewContigTab(String strContigName, 
			Tab parentTab, int nRecordNum, 
			int [] prevDisplayOptions) 
	{
		ContigData theContig = new ContigData();
		theContig.setContigID(strContigName);

		// Make a psuedo cluster with the one contig
		MainData theClusterToView = new MainData();
		theClusterToView.addContig(theContig);

		return addNewClusterTab(theClusterToView, parentTab, nRecordNum,
				strContigName, prevDisplayOptions);
	}

	public Tab addNewClusterTab(MainData theCluster, 
			Tab parentTab, int nRecordNum, 
			String strTabTitle, int [] prevDisplayOptions) 
	{
		Tab tab = new MainTopRowTab(this, theCluster, parentTab, nRecordNum, prevDisplayOptions);
		tabbedPane.addTab(strTabTitle, tab);
		tabbedPane.setSelectedTab(tab);
		return tab;
	}
	
	public MenuTreeNode addNewMenuTreeNode(String parentTitle,
			String nodeTitle, Tab tab, String toolTipText) 
	{
		MenuTreeNode node = menuTree.getNodeWithUserObject(tab);
		if (node == null) {
			node = new MenuTreeNode(nodeTitle);
			menuTree.addNode(parentTitle, node);
			node.setType(MenuTreeNode.TYPE_CLOSEABLE);
		}
		node.setUserObject(tab);
		node.setToolTipText(toolTipText);
		menuTree.setSelected(node); // causes action event

		return node;
	}
	
	public void removeMenuTreeNodeAfterReplace(String parentTitle)
	{
		MenuTreeNode result = menuTree.getNodeWithName(ShowAllContigs);

		Tab sel = tabbedPane.getSelectedTab();
		tabbedPane.remove(sel);
		resultsContigTab.removeResultSummary(sel);
		resultsPairTab.removeResultSummary(sel);
			
		MenuTreeNode capNode = menuTree.getLastSelected();
		
		for(int x=0; x<result.getChildCount(); x++)
		{
			MenuTreeNode temp = result.getChildAt(x);
			
			if(temp.getText().equals(parentTitle))
				menuTree.removeNode(temp);
		}
		menuTree.removeNode(capNode);
		menuTree.setSelected(result);
	}

	private Tab addOverviewTab() {
		Vector<String> tempMsg = new Vector<String>();
		tempMsg.add("Generating statistics ...\n" +
				"This may take a moment the first time.\n" +
				"You can go to another tab if you like.");
		final TextTab newTab = new TextTab(this, null, tempMsg);

		Thread thread = new Thread(new Runnable() {
			public void run() {
				try {
					Vector<String> lines = new Vector<String>();
					selectedSession.overview(lines);
					newTab.setContent(lines);
				} catch (Exception err) {
					newTab.setContent("Failed generating stats.\n"
							+ err.toString());
					ErrorReport.reportError(err);
				}
			}
		});
		thread.setPriority(Thread.MIN_PRIORITY);
		thread.start();

		return newTab;
	}

	public void swapInClusterTab(Tab oldTab, MainData theCluster,
		int nRecordNum, String strTabTitle) 
	{
		// Add the cluster tab (at the end)
		Tab newTab = addNewClusterTab(theCluster, null, nRecordNum, strTabTitle, null);

		// Reposition the cluster tab to the position of the "wait" tab
		MenuTreeNode node = menuTree.getNodeWithUserObject(oldTab);
		if (node != null)
			node.setUserObject(newTab);
		tabbedPane.swapInTab(oldTab, strTabTitle, newTab);
		tabbedPane.setSelectedTab(newTab);		
	}

	// Modification of the Runnable interface so that it can throw to
	// prevent redundant exception handling code.
	public interface RunnableCanThrow {
		public void run() throws Throwable;
	}

	public void addTab(String strTabName, Tab newTab) {
		tabbedPane.addTab(strTabName, newTab);
		tabbedPane.setSelectedTab(newTab);
	}

	public void addTabAndRun(Tab oldTab, String strTabName, Tab newTab,
			RunnableCanThrow toBeRun) {

		/**************** Nested Class ****************/
		class WaitAndRun implements Runnable {
			private RunnableCanThrow holdToBeRun = null;
			private Tab waitTab = null;

			public WaitAndRun(RunnableCanThrow inWaitAndRun, Tab inWaitTab) {
				waitTab = inWaitTab;
				holdToBeRun = inWaitAndRun;
			}

			public void run() {
				try {
					// Wait cursor
					UIHelpers.setFrameWaitCursor(JPaveFrame.this);

					// OK, now run the actual process
					holdToBeRun.run();
				} catch (Throwable err) {
					// Swap the wait tab for an exception tab
					String s = "Internal error: wait and run";
					System.err.println(s);
					ErrorReport.reportError(err, s);
					swapToExceptionTab(waitTab, err);
				}

				// Wait cursor
				UIHelpers.clearFrameCursor(JPaveFrame.this);
			}
		}
		;
		/************* End Nested Class *******************/
		MenuTreeNode n = menuTree.getNodeWithUserObject(oldTab);
		if (n != null)
			n.setUserObject(newTab);

		tabbedPane.swapInTab(oldTab, strTabName, newTab);
		tabbedPane.setSelectedTab(newTab);
		validate(); 
		repaint(); 

		// Add an event to actually run the process after all of the events
		// are processed to display the new tab.
		EventQueue.invokeLater(new WaitAndRun(toBeRun, newTab));
	}

	public void swapInTab(Tab oldTab, String strNewTitle, Tab newTab) {
		// Swap the old tab for the new
		tabbedPane.swapInTab(oldTab/* nOldIndex */, strNewTitle, newTab);

		if (menuTree != null) {
			MenuTreeNode n = menuTree.getNodeWithUserObject(oldTab);
			if (n != null) {
				n.setUserObject(newTab);
			}
		}
	}

	public void swapToExceptionTab(Tab oldTab, Throwable err) {
		if (debug)
			System.err.println("JPaveFrame.swapToExceptionTab: oldTab=" + oldTab);

		// Build up a list of error information
		Vector<String> errorStrings = new Vector<String>();
		if (err instanceof OutOfMemoryError) {
			errorStrings.add("viewSingleTCW ran out of memory.");
			if (theSettings.isApplet()) {
				errorStrings
						.add("First try increasing the size (-Xmx) in the viewSingleTCW file.");
				errorStrings
						.add("If that does not work, increase your memory size as follows:");
				errorStrings.add("");
				errorStrings.add("For Windows:");
				errorStrings
						.add("- From the Start menu button, select Settings, then Control Panel to open the Control Panel.");
				errorStrings
						.add("- Double click the Java icon to open the 'Java Control Panel'.");
				errorStrings.add("- Select the 'Java' tab.");
				errorStrings
						.add("- Click the 'View' button under 'Java Applet Runtime Settings'.");
				errorStrings
						.add("- For each row, click the 'Java Runtime Parameters' column and enter '-Xmx512m' without the quotes.");
				errorStrings
						.add("- Click the 'OK' buttons to close the Control Panel.");
				errorStrings.add("- Restart your browser.");
				errorStrings.add("");
				errorStrings.add("For Unix:");
				errorStrings
						.add("- Open a terminal and change to the Java installation directory (e.g., /usr/jre1.6.0_03).");
				errorStrings
						.add("- Execute bin/ControlPanel to open the 'Java Control Panel'.");
				errorStrings.add("- Select the 'Java' tab.");
				errorStrings
						.add("- Click the 'View' button under 'Java Applet Runtime Settings'.");
				errorStrings
						.add("- For each row, click the 'Java Runtime Parameters' column and enter '-Xmx512m' without the quotes.");
				errorStrings
						.add("- Click the 'OK' buttons to close the Control Panel.");
				errorStrings.add("- Restart your browser.");
				errorStrings.add("");
				errorStrings.add("For Mac:");
				errorStrings
						.add("- In the Finder, select 'Go' then select 'Computer'.");
				errorStrings
						.add("- In the new window, select 'Applications', then 'Utilities', then 'Java'.");
				errorStrings
						.add("- Double-click 'Java Plugin Settings' to open the 'Java Control Panel'.");
				errorStrings
						.add("- Under 'Java Runtime Parameters' enter '-Xmx512m' without the quotes.");
				errorStrings
						.add("- Click the 'Apply Now' button and close the Control Panel.");
				errorStrings.add("- Restart your browser.");
			} else {
				errorStrings
						.add("Increase the Java memory size for your system using the instructions below:");
				errorStrings
						.add("- Edit the viewSingleTCW file.");
				errorStrings.add("- Changed the text '-Xmx512m' to a larger number, e.g '-Xmx768'.");
				errorStrings.add("- Save the file and restartSingleTCW.");
			}
		} else {
			errorStrings
					.add("The viewSingleTCW program encountered an error, we apologize for the inconvenience.");
			errorStrings
					.add("Please send the error description below to tcw@agcol.arizona.edu.");
			errorStrings
					.add("To copy the error description, highlight it with the mouse and press CTRL-C.");
			errorStrings.add("");
			errorStrings.addAll(Converters.getStackTraceAndMessageLines(err));
		}

		err.printStackTrace();

		swapInTab(oldTab, null, new TextTab(null, null, errorStrings));
	}

	public JApplet getApplet() {
		return theSettings.getApplet();
	}

	public SessionData getAssemblyList() {
		return selectedSession;
	}
	
	public Preferences getPreferencesRoot() {
		Preferences root = null;
		try {
			root = Preferences.userRoot();
		} catch (Throwable err) {
			// Create a stub, do nothing object; mostly for running in the
			// debugger
			String s = "Error: cannot load/save user preferences";
			System.err.println(s);
			ErrorReport.reportError(err, s);
			root = new AbstractPreferences(null, "") {
				protected AbstractPreferences childSpi(String name) {
					return this;
				};

				protected String[] childrenNamesSpi() {
					return new String[0];
				};

				protected void flushSpi() {
				};

				protected String getSpi(String key) {
					return null;
				};

				protected String[] keysSpi() {
					return new String[0];
				};

				protected void putSpi(String key, String value) {
				};

				protected void removeSpi(String key) {
				};

				protected void syncSpi() {
				};

				protected void removeNodeSpi() {
				};

				public void flush() {
				};
			};
		}		

		return root.node(prefRootName);
	}

	/*************************************************************
	 * XXX set UI Defaults
	 */
	static public void setupUIDefaults() {
		UIManager.put("CheckBox.font", boldUIFont);
		UIManager.put("Panel.font", boldUIFont);
		UIManager.put("Label.font", boldUIFont);
		UIManager.put("Button.font", boldUIFont);
		UIManager.put("Menu.font", boldUIFont);
		UIManager.put("MenuItem.font", boldUIFont);
		UIManager.put("RadioButtonMenuItem.font", boldUIFont);
		UIManager.put("ComboBox.font", boldUIFont);
		UIManager.put("RadioButton.font", boldUIFont);
		UIManager.put("TabbedPane.font", boldUIFont);

		UIManager.put("TextField.font", textFont);
		UIManager.put("TextArea.font", textFont);
		UIManager.put("List.font", textFont);

		UIManager.put("ScrollBar.background", componentBGColor);
		UIManager.put("ScrollBar.track", componentBGColor);
		UIManager.put("CheckBox.background", componentBGColor);
		UIManager.put("Panel.background", componentBGColor);
		UIManager.put("TabbedPane.background", componentBGColor);
		UIManager.put("TabbedPane.tabAreaBackground", componentBGColor);
		UIManager.put("TabbedPane.selected", componentBGColor);
		UIManager.put("TabbedPane.highlight", componentBGColor);
		UIManager.put("Label.background", componentBGColor);
		UIManager.put("Button.background", componentBGColor);
		UIManager.put("Menu.background", componentBGColor);
		UIManager.put("MenuItem.background", componentBGColor);
		UIManager.put("RadioButtonMenuItem.background", componentBGColor);
		UIManager.put("ComboMenuItem.background", componentBGColor);
		UIManager.put("ComboBox.background", componentBGColor);
		UIManager.put("ComboBox.disabledBackground", componentBGColor);
		UIManager.put("ComboBox.listBackground", componentBGColor);
		UIManager.put("RadioButton.background", componentBGColor);
		UIManager.put("Viewport.background", componentBGColor);
		UIManager
				.put("InternalFrame.inactiveTitleBackground", componentBGColor);
		UIManager.put("ScrollPane.background", componentBGColor);
		UIManager.put("TableHeader.background", componentBGColor);

		UIManager.put("control", componentBGColor);
		UIManager.put("inactiveCaption", componentBGColor);
		UIManager.put("menu", componentBGColor);
		UIManager.put("scrollbar", componentBGColor);
		UIManager.put("windowBorder", componentBGColor);

		UIManager.put("List.selectionBackground", selectColor);
		UIManager.put("TextField.selectionBackground", selectColor);
		UIManager.put("TextArea.selectionBackground", selectColor);
		UIManager.put("ScrollBar.thumb", selectColor);

		UIManager.put("Button.focus", selectColor);
		UIManager.put("RadioButton.focus", selectColor);
		UIManager.put("RadioButton.focus", selectColor);
		UIManager.put("ComboBox.selectionBackground", selectColor);
		UIManager.put("CheckBoxMenuItem.selectionBackground", selectColor);
		UIManager.put("Menu.selectionBackground", selectColor);
		UIManager.put("MenuItem.selectionBackground", selectColor);
		UIManager.put("RadioButtonMenuItem.selectionBackground", selectColor);
		UIManager.put("RadioButton.focus", selectColor);
	}

	static public Font getTextFont() {
		return textFont;
	};

	static public Color getComponentColor() {
		return componentBGColor;
	};

	static public Color getSelectionColor() {
		return selectColor;
	};

	static public Color getAltRowColor() {
		return altRowColor;
	};

	private void cleanupMemory()
	{
		contentPane = null;
		tabbedPane = null;
		viewPane = null;
		splitPane = null;
		menuTree = null;
		lblStatus = null;
		btnExecute = null;
		if(queryTab != null) queryTab.close();
		if(fieldTab != null) fieldTab.close();
		if(basicContigQueryTab != null) basicContigQueryTab.close();
		if(basicHitQueryTab != null) basicHitQueryTab.close();
		if(resultsContigTab != null) resultsContigTab.close();	
		if(resultsPairTab != null) resultsPairTab.close();
		theSettings = null;
		selectedSession = null; 		
	}
	/**
	 * Shutdown methods
	 */
	private class MyShutdown extends Thread { // runs at program exit for each JFrame
		public void run() {
			shutdown();
		}
	}

	private boolean isShutdown = false;

	private synchronized void shutdown() {
		try {
			if (!isShutdown) {
				// remove cache files
				for (Tab t : tabbedPane.getTabs()) {
					t.close();
					//DebugInfo.ShowMemoryPercentFree();
				}
				
				CAP3orPhrapTab.RemoveAllResults();
				ErrorReport.notifyUserOnClose("tcw@agcol.arizona.edu");
				
				isShutdown = true;
			}
		}
		catch (Exception e) {
			ErrorReport.reportError(e, "Error shuting down");
		}
	}

	public QueryTab getQueryTab() { return queryTab;}
	public FieldTab getFieldTab() { return fieldTab;}
	
	/******************************************
	 * Classes
	 */
	private class JPaveFrameManager {
		public JPaveFrameManager() {
			frameList = new Vector<JPaveFrame> ();
		}
		
		public int registerFrame(JPaveFrame newFrame) {
			if(frameList.contains(newFrame))
				return frameList.indexOf(newFrame);
			frameList.add(newFrame);
			
			return frameList.size() - 1;
		}
		
		public JPaveFrame getFrame(int frameID) {
			try {
				return frameList.get(frameID);
			} catch(Exception e) {
				ErrorReport.reportError(e);

			}
			return null;
		}
				
		private Vector<JPaveFrame> frameList = null;
	}
	// called when before assembly display
		// this may get changed per release to deal with schema changes
	private boolean goodVersion (final SessionData session) {
		AssemblyData ad = session.getAssembly(0);
		String dbversion = ad.getDBversion();
		
		double d = Double.parseDouble(dbversion);
		if (d < 1.5) {
			System.err.println("*** Error: cannot display " + ad.getAssemblyID()
				+ " " + ad.getDBversion() + " because old database ");
			return false;
		}

		DatabaseData db = ad.getDBData();		
		
		try { // terminates if problem updating
			Version.updateSchema(
					db.getDB_URL() , db.getDBName(), 
					db.getQueryUser(), db.getQueryPassword()); 
			ad.setDBversion(Version.sDBver);
	
		} catch (Exception e) {
			String s = "Checking and updating schema. Abort loading " + ad.getAssemblyID();
			ErrorReport.reportError(e, s);
			return false;
		}
		return true;
	}
	/******************************************
	 * Variables
	 */

	static int nFrames = 0;

	private static JPaveFrameManager theFramesList = null;
	private int nFrameID = -1;
	public JSplitPane splitPane;
	public MenuTree menuTree;
	public HiddenTabbedPane tabbedPane;
	
	private Container contentPane;
	private JPanel viewPane;
	private JLabel lblStatus = null;
	private JButton btnExecute = null;
	private JButton btnDefaults = null;

	// The number of Cluster View Tabs spawned from here or a contig/cluster list tab
	private QueryTab queryTab = null;
	private FieldTab fieldTab = null;
	private BasicQueryTab basicContigQueryTab = null;
	private BasicQueryTab basicHitQueryTab = null;
	private BasicGOQueryTab basicGOQueryTab = null;
	private ResultsSummaryTab resultsContigTab = null, resultsPairTab = null;
	private BlastTab blastTab  = null;
	
	private GlobalSettings theSettings = null; // The settings for how viewPAVE should be configured
	public SessionData selectedSession = null; // Should always be synchronized with value in "theSettings"

	public String lastSaveFilePath = ""; // All panels use this as the path, and set it if changed

	private String strUniqueID;
	
	private boolean bHasContigSets = false;
	private boolean bHasNs = false;
	private boolean bHasExpLevels = false;
	private boolean bHasGOs = false;
	private boolean bHasAssembly = false;
	private boolean bHasKEGG = false;
	private boolean bHasPFAM = false;
	private boolean bHasEC = false;
	private boolean bHasDBhits = false;
	
	private int nContigs = 0;
	private boolean bHasBuried = false;
	private boolean bHasMatePairs = false;
	private boolean bHasPairwise = false;
	private boolean bHasSelfblast = false;
	private boolean bHasTselfblast = false;
	private boolean bIsProteinDB = false;
	private boolean bHasLoc = false;
	private int nContigSets = 0;

	public static final int BASIC_QUERY_MODE_UNITRANS = 0;
	public static final int BASIC_QUERY_MODE_HIT = 1;
	public static final int BASIC_QUERY_MODE_GO = 2;
	/******************************
	 * Constants
	 */
	private static final long serialVersionUID = 1;
	
	public static final String GeneralSection = "General ";
	public static final String Instructions = "Instructions";
	public static final String HTML = "/html/viewSingleTCW/instructions.html";
	public static final String Overview = "Overview";
	public static final String Blast = "Blast";
	public static final String RestoreDefaults = "Restore Defaults";
	
	public static final String BasicSection = "Basic Queries ";
	public static final String BasicContigQuery = "Sequences";
	public static final String BasicHitQuery = "annoDB Hits";
	public static final String BasicGOQuery = "GO annotations"; 
	
	public static final String SequenceSection = "Sequence ";
	public static final String ShowAllContigs = "Show All";
	public static final String FilterContigsQuery = "Filters";
	public static final String SelectContigsColumns = "Columns";
	public static final String ListContigsQueries = "Results";
	
	public static final String ShowFilteredContigs = "View Filtered Sequences";
	//public static final String ViewAlignedContigs = "Align Selected Contigs";
	public static final String ShowBasicContig = "View Selected Sequence";
	
	public static final String PairsSection = "Similar Sequence Pairs ";
	public static final String ShowAllPairs = "Show Pairs";
	public static final String FilterPairsQuery = "Pairs Filters";
	public static final String ListPairsQueries = "Pairs Results";
	public static final String SelectPairsColumns = "Pairs Columns";
	public static final String ShowFilteredPairs = "View Filtered Pairs";
	
	// Contstants for colors and fonts
	static private final Font boldUIFont = new Font("Sans", Font.BOLD, 12);
	static private final Font textFont = new Font("Sans", Font.PLAIN, 11);
	static private final Color componentBGColor = Color.white;
	static private final Color selectColor = new Color(0xB0C4DE);
	static private final Color altRowColor = new Color(0xEEF2FF);
}
